package examples.bombing;

import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.Behaviour;
import jade.core.behaviours.OneShotBehaviour;
import jade.core.behaviours.TickerBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.lang.acl.UnreadableException;

import java.io.IOException;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

/**
 * Class description goes here
 * 
 * @author Artjom Kruglenkov - Molodez :P
 * @version 1.0
 */
public class SmartBomb2 extends Agent {

    private static final long serialVersionUID = 1L;

    private static final String BOMB_DETONATION_SERVICE_TYPE = "bomb-detonation";
    // The expected number of casualties after detonation
    private int numberOfCasualties = 0;

    // Agent initialization
    protected void setup() {
        System.out.println("Hello! Bomb-agent " + getAID().getName() + " is ready.");

        DFAgentDescription dfd = new DFAgentDescription();
        dfd.setName(getAID());
        ServiceDescription sd = new ServiceDescription();
        sd.setType(BOMB_DETONATION_SERVICE_TYPE);
        sd.setName("JADE-chain-bombing");
        dfd.addServices(sd);

        try {
            DFService.register(this, dfd);
        } catch (FIPAException e) {
            e.printStackTrace();
            throw new RuntimeException("Can't register smart bomb!");
        }

        addBehaviour(new DetonatorBehaviour());
        addBehaviour(new CasualtiesResponseBehaviour());
        addBehaviour(new CasualtiesUpdateBehaviour());
    }

    protected void takeDown() {
        try {
            DFService.deregister(this);
        } catch (FIPAException e) {
            e.printStackTrace();
        }
        System.out.println("Bomb-agent " + getAID().getName() + " terminating.");
    }

    private class DetonatorBehaviour extends TickerBehaviour {

        private static final int GET_BOMPS_STEP = 0;
        private static final int REQUEST_CASUALTIES_STEP = 1;
        private static final int RECEIVE_CASUALTIES = 2;
        private static final int CHECK_CASUALTIES_STEP = 3;
        private static final int DETONATE = 4;

        private static final long serialVersionUID = 1L;
        private AID bombs[];
        private int step = GET_BOMPS_STEP;
        private Set<String> conversations = new HashSet<String>();
        private int numberOfBombsReady = 0;

        public DetonatorBehaviour() {
            super(SmartBomb2.this, 1000);
        }

        @Override
        protected void onTick() {
            if (checkDetonation()) {
                step = DETONATE;
            }
            switch (step) {
                case GET_BOMPS_STEP: {
                    bombs = getBombs();
                    step++;
                    break;
                }
                case REQUEST_CASUALTIES_STEP: {
                    sendCasualitiesRequest();
                    step++;
                    break;
                }
                case RECEIVE_CASUALTIES: {
                    receiveCasualities();
                    if (conversations.isEmpty()) {
                        step++;
                    }
                    break;
                }
                case CHECK_CASUALTIES_STEP: {
                    System.out.println(getName() + " : Check casualities");
                    if (numberOfCasualties >= 1000) {
                        numberOfBombsReady++;
                    }
                    if (numberOfBombsReady >= 3) {
                        step = DETONATE;
                    } else {
                        numberOfBombsReady = 0;
                        step = 0;
                    }
                    break;
                }
                case DETONATE: {
                    detonate();
                    stop();
                    break;
                }
                default: {
                    throw new RuntimeException("Something went wrong!");
                }
            }
        }

        private boolean checkDetonation() {
            MessageTemplate tmp = MessageTemplate.MatchPerformative(ACLMessage.INFORM);
            ACLMessage msg = receive(tmp);
            if (msg != null) {
                System.out.println(getName() + " DETONATE FROM: " + msg.getContent());
                return true;
            }
            return false;
        }

        private void detonate() {
            if (bombs == null) {
                bombs = getBombs();
            }
            System.out.println(getName() + " : Initiate detonation!");
            myAgent.addBehaviour(new BlastBehaviour());
            for (AID aid : bombs) {
                ACLMessage msg = new ACLMessage(ACLMessage.INFORM);
                msg.addReceiver(aid);
                send(msg);
            }
        }

        private void sendCasualitiesRequest() {
            for (AID aid : bombs) {
                if (aid.equals(getAID())) {// Don't send to itself
                    continue;
                }
                ACLMessage msg = new ACLMessage(ACLMessage.REQUEST);
                msg.setReplyWith("CASUALTIES_REQUEST");
                String conversationId = getName() + ":" + aid.getName() + ":CASUALTIES_REQUEST:";
                conversations.add(conversationId);
                msg.setConversationId(conversationId);
                msg.addReceiver(aid);
                System.out.println(getName() + " : Send casualties request to " + aid.getName());
                send(msg);
            }
        }

        private void receiveCasualities() {
            for (AID aid : bombs) {
                if (aid.equals(getAID())) {
                    continue;
                }
                String conversationId = getName() + ":" + aid.getName() + ":CASUALTIES_REQUEST:";
                MessageTemplate tmp = MessageTemplate.MatchConversationId(conversationId);
                System.out.println(getName() + " : Wait response from " + aid.getName());
                ACLMessage msg = receive(tmp);
                if (msg != null) {
                    try {
                        Long cas = (Long) msg.getContentObject();
                        System.out.println(getName() + " : Received " + cas + " from " + msg.getSender().getName());
                        if (cas > 1000) {
                            numberOfBombsReady++;
                        }
                        conversations.remove(conversationId);
                    } catch (UnreadableException e) {
                        System.out.println(getName() + " BEEEEE!");
                    }
                }
            }
        }

        private AID[] getBombs() {
            DFAgentDescription template = new DFAgentDescription();
            ServiceDescription sd = new ServiceDescription();
            sd.setType(BOMB_DETONATION_SERVICE_TYPE);
            template.addServices(sd);
            AID[] bombs = new AID[] {};

            try {
                DFAgentDescription[] result = DFService.search(myAgent, template);
                bombs = new AID[result.length];
                for (int i = 0; i < result.length; ++i) {
                    bombs[i] = result[i].getName();
                }
            } catch (FIPAException e) {
                e.printStackTrace();
            }
            return bombs;
        }

    }

    private class CasualtiesResponseBehaviour extends Behaviour {

        private static final long serialVersionUID = 1L;

        @Override
        public void action() {
            MessageTemplate requestFilter = MessageTemplate.MatchPerformative(ACLMessage.REQUEST);
            MessageTemplate inReplyFilter = MessageTemplate.not(MessageTemplate.MatchInReplyTo("CASUALTIES_REQUEST"));
            MessageTemplate messageFilter = MessageTemplate.and(requestFilter, inReplyFilter);
            ACLMessage msg = myAgent.receive(messageFilter);
            if (msg != null) {
                try {
                    ACLMessage replay = msg.createReply();
                    replay.setContentObject(new Long(numberOfCasualties));
                    System.out.println(getName() + ": Send number of casualties to" + msg.getSender().getName() + ", inReplyTo " + replay.getInReplyTo());
                    send(replay);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } else {
                block();
            }
        }

        @Override
        public boolean done() {
            return false;
        }

    }

    private class CasualtiesUpdateBehaviour extends TickerBehaviour {

        private static final long serialVersionUID = 1L;

        public CasualtiesUpdateBehaviour() {
            super(SmartBomb2.this, 3000);
        }

        @Override
        protected void onTick() {
            numberOfCasualties = new Random().nextInt(2000);
            System.out.println(getName() + " : Number of casualties: " + numberOfCasualties);
        }

    }

    private class BlastBehaviour extends OneShotBehaviour {

        private static final long serialVersionUID = 1L;

        @Override
        public void action() {
            System.out.println(getName() + ": BOOOOOOOM!!!");
            doDelete();
        }
    }
    
}
